﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace RobotGameData
{
    // thông tin map
    public class Map
    {
        #region "Properties"
        // texture name
        private String textureName;
        // texture tiles
        private Texture2D texture;
        // số lượng tile theo width và height trong map
        private Point mapDimensions;
        // kích thước width và height của một tile
        private Point tileSize;
        // số lượng tile trong một hàng
        private int tilesPerRow;

        public string TextureName
        {
            get { return textureName; }
            set { textureName = value; }
        }

        public Texture2D Texture
        {
            get { return texture; }
            set { texture = value; }
        }

        public Point MapDimensions
        {
            get { return mapDimensions; }
            set { mapDimensions = value; }
        }

        public Point TileSize
        {
            get { return tileSize; }
            set { tileSize = value; }
        }

        public int TilesPerRow
        {
            get { return tilesPerRow; }
            set { tilesPerRow = value; }
        }

        #endregion

        #region "Map Layers"

        private int[] baseLayer;

        public int[] BaseLayer
        {
            get { return baseLayer; }
            set { baseLayer = value; }
        }


        public int GetBaseLayerValue(Point mapPosition)
        {
            // check the parameter
            if ((mapPosition.X < 0) || (mapPosition.X >= mapDimensions.X) ||
                (mapPosition.Y < 0) || (mapPosition.Y >= mapDimensions.Y))
            {
                throw new ArgumentOutOfRangeException("mapPosition");
            }

            return baseLayer[mapPosition.Y * mapDimensions.X + mapPosition.X];
        }

        public Rectangle GetBaseLayerSourceRectangle(Point mapPosition)
        {
            // check the parameter, but out-of-bounds is nonfatal
            if ((mapPosition.X < 0) || (mapPosition.X >= mapDimensions.X) ||
                (mapPosition.Y < 0) || (mapPosition.Y >= mapDimensions.Y))
            {
                return Rectangle.Empty;
            }

            int baseLayerValue = GetBaseLayerValue(mapPosition);
            if (baseLayerValue < 0)
            {
                return Rectangle.Empty;
            }

            return new Rectangle(
                (baseLayerValue % tilesPerRow) * tileSize.X,
                (baseLayerValue / tilesPerRow) * tileSize.Y,
                tileSize.X, tileSize.Y);
        }

        private int[] fringeLayer;

        public int[] FringeLayer
        {
            get { return fringeLayer; }
            set { fringeLayer = value; }
        }

        public int GetFringeLayerValue(Point mapPosition)
        {
            // check the parameter
            if ((mapPosition.X < 0) || (mapPosition.X >= mapDimensions.X) ||
                (mapPosition.Y < 0) || (mapPosition.Y >= mapDimensions.Y))
            {
                throw new ArgumentOutOfRangeException("mapPosition");
            }

            return fringeLayer[mapPosition.Y * mapDimensions.X + mapPosition.X];
        }

        public Rectangle GetFringeLayerSourceRectangle(Point mapPosition)
        {
            // check the parameter, but out-of-bounds is nonfatal
            if ((mapPosition.X < 0) || (mapPosition.X >= mapDimensions.X) ||
                (mapPosition.Y < 0) || (mapPosition.Y >= mapDimensions.Y))
            {
                return Rectangle.Empty;
            }

            int fringeLayerValue = GetFringeLayerValue(mapPosition);
            if (fringeLayerValue < 0)
            {
                return Rectangle.Empty;
            }

            return new Rectangle(
                (fringeLayerValue % tilesPerRow) * tileSize.X,
                (fringeLayerValue / tilesPerRow) * tileSize.Y,
                tileSize.X, tileSize.Y);
        }


        private int[] objectLayer;

        public int[] ObjectLayer
        {
            get { return objectLayer; }
            set { objectLayer = value; }
        }

        public int GetObjectLayerValue(Point mapPosition)
        {
            // check the parameter
            if ((mapPosition.X < 0) || (mapPosition.X >= mapDimensions.X) ||
                (mapPosition.Y < 0) || (mapPosition.Y >= mapDimensions.Y))
            {
                throw new ArgumentOutOfRangeException("mapPosition");
            }

            return objectLayer[mapPosition.Y * mapDimensions.X + mapPosition.X];
        }

        public Rectangle GetObjectLayerSourceRectangle(Point mapPosition)
        {
            // check the parameter, but out-of-bounds is nonfatal
            if ((mapPosition.X < 0) || (mapPosition.X >= mapDimensions.X) ||
                (mapPosition.Y < 0) || (mapPosition.Y >= mapDimensions.Y))
            {
                return Rectangle.Empty;
            }

            int objectLayerValue = GetObjectLayerValue(mapPosition);
            if (objectLayerValue < 0)
            {
                return Rectangle.Empty;
            }

            return new Rectangle(
                (objectLayerValue % tilesPerRow) * tileSize.X,
                (objectLayerValue / tilesPerRow) * tileSize.Y,
                tileSize.X, tileSize.Y);
        }

        private int[] collisionLayer;

        public int[] CollisionLayer
        {
            get { return collisionLayer; }
            set { collisionLayer = value; }
        }

        public int GetCollisionLayerValue(Point mapPosition)
        {
            // check the parameter
            if ((mapPosition.X < 0) || (mapPosition.X >= mapDimensions.X) ||
                (mapPosition.Y < 0) || (mapPosition.Y >= mapDimensions.Y))
            {
                throw new ArgumentOutOfRangeException("mapPosition");
            }

            return collisionLayer[mapPosition.Y * mapDimensions.X + mapPosition.X];
        }

        public bool IsBlocked(Point mapPosition)
        {
            // check the parameter, but out-of-bounds is nonfatal
            if ((mapPosition.X < 0) || (mapPosition.X >= mapDimensions.X) ||
                (mapPosition.Y < 0) || (mapPosition.Y >= mapDimensions.Y))
            {
                return true;
            }

            return (GetCollisionLayerValue(mapPosition) != 0);
        }

        #endregion

        #region "Transform" 
        public Point PointToTile(Vector2 worldPoint)
        {
            if (worldPoint.X < 0 || worldPoint.Y < 0 || worldPoint.X > mapDimensions.X * tileSize.X || worldPoint.Y > mapDimensions.Y * tileSize.Y)
            {
                return Point.Zero;
            }

            Point p = new Point();

            // simple conversion to tile indices
            p.X = (int)Math.Floor(worldPoint.X / tileSize.X);
            p.Y = (int)Math.Floor(worldPoint.Y / tileSize.Y);

            // check the upper limit edges. if we are on the edge, we need to decrement the index to keep in bounds.
            if (worldPoint.X == mapDimensions.X * tileSize.X)
            {
                p.X--;
            }
            if (worldPoint.Y == mapDimensions.Y * tileSize.Y)
            {
                p.Y--;
            }

            return p;
        }
        #endregion

        #region "Map Content"
        private List<Portal> portals = new List<Portal>();

        public List<Portal> Portals
        {
            get { return portals; }
            set { portals = value; }
        }
        #endregion
        public Point EndTile
        {
            get { return endTile; }
            set { endTile = value; }
        }
        private Point startTile;

        public Point StartTile
        {
            get { return startTile; }
            set { startTile = value; }
        }
        private Point endTile;

        /// <summary>
        /// Finds the minimum number of tiles it takes to move from Point A to 
        /// Point B if there are no barriers in the way
        /// </summary>
        /// <param name="pointA">Start position</param>
        /// <param name="pointB">End position</param>
        /// <returns>Distance in tiles</returns>
        public static int StepDistance(Point pointA, Point pointB)
        {
            int distanceX = Math.Abs(pointA.X - pointB.X);
            int distanceY = Math.Abs(pointA.Y - pointB.Y);

            return distanceX + distanceY;
        }

        /// <summary>
        /// Finds the minimum number of tiles it takes to move from the current 
        /// position to the end location on the Map if there are no barriers in 
        /// the way
        /// </summary>
        /// <param name="point">Current position</param>
        /// <returns>Distance to end in tiles</returns>
        public int StepDistanceToEnd(Point point)
        {
            return StepDistance(point, endTile);
        }

        /// <summary>
        /// Enumerate all the map locations that can be entered from the given 
        /// map location
        /// </summary>
        public IEnumerable<Point> OpenMapTiles(Point mapLoc)
        {
            if (!IsBlocked(new Point(mapLoc.X, mapLoc.Y + 1)))
                yield return new Point(mapLoc.X, mapLoc.Y + 1);
            if (!IsBlocked(new Point(mapLoc.X, mapLoc.Y - 1)))
                yield return new Point(mapLoc.X, mapLoc.Y - 1);
            if (!IsBlocked(new Point(mapLoc.X + 1, mapLoc.Y)))
                yield return new Point(mapLoc.X + 1, mapLoc.Y);
            if (!IsBlocked(new Point(mapLoc.X - 1, mapLoc.Y)))
                yield return new Point(mapLoc.X - 1, mapLoc.Y);
        }

        public bool InMap(Point mapPosition)
        {
            // check the parameter, but out-of-bounds is nonfatal
            if ((mapPosition.X < 0) || (mapPosition.X >= mapDimensions.X) ||
                (mapPosition.Y < 0) || (mapPosition.Y >= mapDimensions.Y))
            {
                return false;
            }
            return true;
           
        }
        /// <summary>
        /// Translates a map tile location into a screen position
        /// </summary>
        /// <param name="location">map location</param>
        /// <param name="centered">true: return the location of the center of the tile
        /// false: return the position of the upper-left corner of the tile</param>
        /// <returns>screen position</returns>
        public Vector2 MapToWorld(Point location, bool centered)
        {
            Vector2 screenPosition = new Vector2();

            if (InMap(location))
            {
                screenPosition.X = location.X * tileSize.X;
                screenPosition.Y = location.Y * tileSize.Y;
                if (centered)
                {
                    screenPosition += new Vector2(tileSize.X / 2, tileSize.Y / 2);
                }
            }
            else
            {
                screenPosition = Vector2.Zero;
            }
            return screenPosition;
        }

    }
}
